#!/usr/bin/env python3

# This tool is a helper script that queries the admin address for all listener
# addresses after envoy startup. (The admin address is written out to a file by
# setting the -a flag in the envoy binary.) The script then outputs a new json
# config file with updated listener addresses. This script is currently called
# in the hot restart integration test to update listener addresses bound to
# port 0 in the initial json config file.

from collections import OrderedDict

import argparse
import http.client
import json
import os.path
import re
import sys
import time

# Seconds to wait for the admin address output file to appear. The script exits
# with failure if the file is not found.
ADMIN_FILE_TIMEOUT_SECS = 20


# Because the hot restart files are yaml but yaml support is not included in
# python by default, we parse this fairly manually.
def GenerateNewConfig(original_yaml, admin_address, updated_json):
  # Get original listener addresses
  with open(original_yaml, 'r') as original_file:
    sys.stdout.write('Admin address is ' + admin_address + '\n')
    try:
      admin_conn = http.client.HTTPConnection(admin_address)
      admin_conn.request('GET', '/listeners?format=json')
      admin_response = admin_conn.getresponse()
      if not admin_response.status == 200:
        return False
      discovered_listeners = json.loads(admin_response.read().decode('utf-8'))
    except Exception as e:
      sys.stderr.write('Cannot connect to admin: %s\n' % e)
      return False
    else:
      raw_yaml = original_file.readlines()
      index = 0
      for discovered in discovered_listeners['listener_statuses']:
        replaced = False
        if 'pipe' in discovered['local_address']:
          path = discovered['local_address']['pipe']['path']
          for index in range(index + 1, len(raw_yaml) - 1):
            if 'pipe:' in raw_yaml[index] and 'path:' in raw_yaml[index + 1]:
              raw_yaml[index + 1] = re.sub('path:.*', 'path: "' + path + '"', raw_yaml[index + 1])
              replaced = True
              break
        else:
          addr = discovered['local_address']['socket_address']['address']
          port = str(discovered['local_address']['socket_address']['port_value'])
          if addr[0] == '[':
            addr = addr[1:-1]  # strip [] from ipv6 address.
          for index in range(index + 1, len(raw_yaml) - 2):
            if ('socket_address:' in raw_yaml[index] and 'address:' in raw_yaml[index + 1] and
                'port_value:' in raw_yaml[index + 2]):
              raw_yaml[index + 1] = re.sub('address:.*', 'address: "' + addr + '"',
                                           raw_yaml[index + 1])
              raw_yaml[index + 2] = re.sub('port_value:.*', 'port_value: ' + port,
                                           raw_yaml[index + 2])
              replaced = True
              break
        if replaced:
          sys.stderr.write('replaced listener at line ' + str(index) + ' with ' + str(discovered) +
                           '\n')
        else:
          sys.stderr.write('Failed to replace a discovered listener ' + str(discovered) + '\n')
          return False
      with open(updated_json, 'w') as outfile:
        outfile.writelines(raw_yaml)
    finally:
      admin_conn.close()

  return True


if __name__ == '__main__':
  parser = argparse.ArgumentParser(description='Replace listener addressses in json file.')
  parser.add_argument('-o',
                      '--original_json',
                      type=str,
                      required=True,
                      help='Path of the original config json file')
  parser.add_argument('-a',
                      '--admin_address_path',
                      type=str,
                      required=True,
                      help='Path of the admin address file')
  parser.add_argument('-u',
                      '--updated_json',
                      type=str,
                      required=True,
                      help='Path to output updated json config file')
  args = parser.parse_args()
  admin_address_path = args.admin_address_path

  # Read admin address from file
  counter = 0
  while not os.path.exists(admin_address_path):
    time.sleep(1)
    counter += 1
    if counter > ADMIN_FILE_TIMEOUT_SECS:
      break

  if not os.path.exists(admin_address_path):
    sys.exit(1)

  with open(admin_address_path, 'r') as admin_address_file:
    admin_address = admin_address_file.read()

  success = GenerateNewConfig(args.original_json, admin_address, args.updated_json)

  if not success:
    sys.exit(1)
